#pragma once
#ifndef __ND_VECTOR_H_
#define __ND_VECTOR_H_
#include <math.h>
#include <stdarg.h>
#include "MathError.h"


#ifndef uint
#define uint unsigned int
#endif



// NDV => 14 + 4 + 22 = 0x28
class NDVectorError : public BaseException
{
public:
  // Define errCodes
  static const short SIZE_NOT_MATCH = 0x2800;
  static const short OUT_OF_BOUNDARY = 0x2801;
  static const short SIZE_NOT_SUPPORT = 0x2802;

  NDVectorError ()
  {
    __errMap.insert (std::make_pair (SIZE_NOT_MATCH, "The sizes of these two vector do not match"));
    __errMap.insert (std::make_pair (OUT_OF_BOUNDARY, "The index is out of boundary"));
    __errMap.insert (std::make_pair (SIZE_NOT_SUPPORT, "The operator is not supported in this size"));
  }
};



/*
NDVector.hpp contains class NDVector< VType, size >
Which is a array contains n elements of Type VType
and support most of operations of vector (in math)
such as plus, minus, multiplied-by-number,
dot-multiply, cross-multiply, unitary
and calculate its length, the angle between two vectors.
See the definition for detail.
NOTE: To support math operations such as unitary and calculate length,
      you mush specified VType as a basic type such as int, float, etc.
Author: NianchenDeng (5080379114)
*/

template< class VType, uint size >
class NDVector
{
private:
  VType* _arr;

  void _assignValue (const VType* coordinate);
  void _assignValue (const VType* vertex1, const VType* vertex2);

public:
	NDVector ();
	NDVector (const VType* startVertexSet, const VType* endVertexSet);
  NDVector (VType element0, VType element1 = VType (), VType element2 = VType (), VType element3 = VType ());
	NDVector (const VType* source);
  NDVector (const NDVector< VType, size >& source);
	~NDVector ();

	NDVector& operator= (const NDVector< VType, size >& right);
  NDVector& operator+= (const NDVector< VType, size >& right);
  NDVector& operator-= (const NDVector< VType, size >& right);
  NDVector& operator*= (const VType& right);
  NDVector& operator/= (const VType& right);
  NDVector& normalize ();

	double getLength () const;
	VType& operator[] (uint pos) const;
	const VType* getData () const;

	NDVector operator+ (const NDVector< VType, size >& right) const;
	NDVector operator- (const NDVector< VType, size >& right) const;
	NDVector operator* (const VType& right) const;
	NDVector operator/ (const VType& right) const;
  NDVector operator- () const;
	double dot (const NDVector< VType, size >& right) const;
	NDVector cross (const NDVector< VType, size >& right) const;

	double angle (const NDVector< VType, size >& right) const;
  double distance (const NDVector< VType, size >& right) const;

  bool operator== (const NDVector< VType, size >& right) const;
  bool operator!= (const NDVector< VType, size >& right) const;

}; // End of Class NDVector< VType, size > definition


typedef NDVector< float, 2 > Vector2f;
typedef NDVector< float, 3 > Vector3f;
typedef NDVector< float, 4 > Vector4f;
typedef NDVector< int, 2 > Vector2i;
typedef NDVector< int, 3 > Vector3i;
typedef NDVector< int, 4 > Vector4i;
typedef NDVector< double, 2 > Vector2d;
typedef NDVector< double, 3 > Vector3d;
typedef NDVector< double, 4 > Vector4d;





// ========================================================================
// Constructors & Destructor
// ========================================================================

// Construct an zero vector
template< class VType, uint size >
NDVector< VType, size >::NDVector ()
{
  _arr = new VType[size];
  for (uint i = 0; i < size; ++i)
    _arr[i] = VType ();
}

// Construct a vector point from vertex1 to vertex2
template< class VType, uint size >
NDVector< VType, size >::NDVector (const VType *startVertexSet,
  const VType *endVertexSet)
{
  _arr = new VType[size];
  _assignValue (startVertexSet, endVertexSet);
}

// Construct a vector by an array
template< class VType, uint size >
NDVector< VType, size >::NDVector (const VType *source)
{
  _arr = new VType[size];
  _assignValue (source);
}

// Construct a vector by coordinate
template< class VType, uint size >
NDVector< VType, size >::NDVector (VType element0, VType element1, VType element2, VType element3)
{
  _arr = new VType[size];
  _arr[0] = element0;
  if (size >= 2)
    _arr[1] = element1;
  if (size >= 3)
    _arr[2] = element2;
  if (size >= 4)
    _arr[3] = element3;
//   va_list arg_ptr;
//   va_start (arg_ptr, element0);
//   for (uint i = 1; i < size; ++i)
//     _arr[i] = va_arg (arg_ptr, VType);
//   va_end (arg_ptr);
}

// Copy constructor
template< class VType, uint size >
NDVector< VType, size >::NDVector (const NDVector< VType, size >& source)
{
  _arr = new VType[size];
  _assignValue (source.getData ());
}

template< class VType, uint size >
NDVector< VType, size >::~NDVector ()
{
  delete[] _arr;
}

// ========================================================================
// Self-operation functions
// ========================================================================

template< class VType, uint size >
NDVector< VType, size >& NDVector< VType, size >::operator= (const NDVector< VType, size >& right)
{
  _assignValue (right.getData ());
  return (*this);
}

template< class VType, uint size >
NDVector< VType, size >& NDVector< VType, size >::operator+= (const NDVector< VType, size >& right)
{
  for (uint i = 0; i < size; ++i)
    _arr[i] += right[i];
  return (*this);
}


template< class VType, uint size >
NDVector< VType, size >& NDVector< VType, size >::operator-= (const NDVector< VType, size >& right)
{
  for (uint i = 0; i < size; ++i)
    _arr[i] -= right[i];
  return (*this);
}


template< class VType, uint size >
NDVector< VType, size >& NDVector< VType, size >::operator*= (const VType& right)
{
  for (int i = 0; i < size; ++i)
    _arr[i] *= right;
  return (*this);
}

template< class VType, uint size >
NDVector< VType, size >& NDVector< VType, size >::operator/= (const VType& right)
{
  if (right == 0)
    CTHROW (MathError, MathError::DIVIDE_ZERO_ERROR);
  for (int i = 0; i < size; ++i)
    _arr[i] /= right;
  return (*this);
}

template< class VType, uint size >
NDVector< VType, size >& NDVector< VType, size >::normalize ()
{
  double length = getLength ();
  if (length != 0)
    for (uint i = 0; i < size; ++i)
      _arr[i] /= length;
  return (*this);
}

// ========================================================================
// Get functions
// ========================================================================

template< class VType, uint size >
double NDVector< VType, size >::getLength () const
{
  long double sum = 0.0;
  for (uint i = 0; i < size; ++i)
    sum += _arr[i] * _arr[i];
  return sqrt (sum);
}

template< class VType, uint size >
VType& NDVector< VType, size >::operator[] (uint pos) const
{
  if (pos >= size)
    CTHROW (NDVectorError, NDVectorError::OUT_OF_BOUNDARY);
  return _arr[pos];
}

template< class VType, uint size >
const VType* NDVector< VType, size >::getData () const
{
  return _arr;
}

// ========================================================================
// Operation functions
// ========================================================================

template< class VType, uint size >
NDVector< VType, size > NDVector< VType, size >::operator+ (const NDVector< VType, size >& right) const
{
  NDVector< VType, size > answer;
  for (uint i = 0; i < size; ++i)
    answer[i] = _arr[i] + right._arr[i];
  return answer;
}


template< class VType, uint size >
NDVector< VType, size > NDVector< VType, size >::operator- (const NDVector< VType, size >& right) const
{
  NDVector< VType, size > answer;
  for (uint i = 0; i < size; ++i)
    answer[i] = _arr[i] - right._arr[i];
  return answer;
}

//Here are two forms of number-multiply operator
template< class VType, uint size >
NDVector< VType, size > NDVector< VType, size >::operator* (const VType& right) const
{
  NDVector< VType, size > answer;
  for (uint i = 0; i < size; ++i)
    answer[i] = _arr[i] * right;
  return answer;
}

template< class VType, uint size >
NDVector< VType, size > NDVector< VType, size >::operator/ (const VType& right) const
{
  if (right == 0)
    CTHROW (MathError, MathError::DIVIDE_ZERO_ERROR);
  NDVector< VType, size > answer;
  for (uint i = 0; i < size; ++i)
    answer[i] = _arr[i] / right;
  return answer;
}

//This is dot-multiply operator
template< class VType, uint size >
double NDVector< VType, size >::dot (const NDVector< VType, size >& right) const
{
  double answer = 0;
  for (uint i = 0; i < size; ++i)
    answer += (double) (_arr[i] * right[i]);
  return answer;
}

template< class VType, uint size >
NDVector< VType, size > NDVector< VType, size >::cross (const NDVector< VType, size >& right) const
{
  if (size != 3)
    CTHROW (NDVectorError, NDVectorError::SIZE_NOT_SUPPORT);
  NDVector< VType, size > answer;
  answer[0] = _arr[1] * right[2] - _arr[2] * right[1];
  answer[1] = _arr[2] * right[0] - _arr[0] * right[2];
  answer[2] = _arr[0] * right[1] - _arr[1] * right[0];
  return answer;
}

template< class VType, uint size >
NDVector< VType, size > NDVector< VType, size >::operator- () const
{
  NDVector< VType, size > ans = *this;
  for (uint i = 0; i < size; ++i)
    ans._arr[i] = -ans._arr[i];
  return ans;
}

// ========================================================================
// Relation functions
// ========================================================================

//The return value is in radian, not degree
template< class VType, uint size >
double NDVector< VType, size >::angle (const NDVector< VType, size >& right) const
{
  double thisLength = getLength ();
  double rightLength = right.getLength ();
  if (thisLength == 0 || rightLength == 0)
    return 0;
  double dotResult = this->dot (right);
  double lengthResult = thisLength * rightLength;
  if (dotResult > lengthResult) return 0;
  return acos (dotResult / lengthResult);
}

template< class VType, uint size >
double NDVector< VType, size >::distance (const NDVector< VType, size >& right) const
{
  NDVector< VType, size > temp (_arr, right._arr);
  return temp.getLength ();
}

template< class VType, uint size >
bool NDVector<VType, size>::operator!= (const NDVector< VType, size >& right) const
{
  return !((*this) == right);
}
template< class VType, uint size >
bool NDVector<VType, size>::operator== (const NDVector< VType, size >& right) const
{
  for (uint i = 0; i < size; ++i)
  {
    if (_arr[i] != right[i])
      return false;
  }
  return true;
}

// ========================================================================
// Private functions
// ========================================================================

//Get value from an array
template< class VType, uint size >
void NDVector< VType, size >::_assignValue (const VType* coordinates)
{
  for (uint i = 0; i < size; ++i)
    _arr[i] = coordinates[i];
}

//Get value from two vertexes
//to set the vector pointing from vertex1 to vertex2
template< class VType, uint size >
void NDVector< VType, size >::_assignValue (const VType* vertex1, const VType* vertex2)
{
  for (uint i = 0; i < size; ++i)
    _arr[i] = vertex2[i] - vertex1[i];
}



#endif